// BonusBingoGameResult.cpp: implementation of the BonusBingoGameResult class.
//
//////////////////////////////////////////////////////////////////////
#include <windows.h>

#include "BonusBingoGameResult.h"
#include "BonusBingoGame.h"
#include "BonusBingoCard.h"
#include "log.h"

#include <algorithm>
#include <iterator> // CSN-12948 - Upgrade to VS2012

/******************************************************************************
Static initializations & Methods
******************************************************************************/
const int32 BonusBingoGameResult::g_Payouts[BonusBingo::WIN_PATTERN_COUNT] = 
{   
    //The default payouts are defined in shared/BonusBingoConstants.h
    SLASH_PAYOUT,         
    BACKSLASH_PAYOUT,
    CORNERS_PAYOUT,
    TWO_LINES_HORIZ_PAYOUT,
    CROSS_PAYOUT,
    T_PATTERN_PAYOUT,
    THREE_LINES_HORIZ_PAYOUT,
    PERIMETER_PAYOUT,
    FOUR_LINES_HORIZ_PAYOUT,
    BINGO_PAYOUT,
};

const int32 BonusBingoGameResult::HitMasks[ BonusBingo::WIN_PATTERN_COUNT ] = 
{
    SlashHitMask,
    BackSlashHitMask,
    CornersHitMask,
    TwoRowsHitMask,
    CrossHitMask,
    TPatternHitMask,
    ThreeRowsHitMask,
    PerimeterHitMask,   
    FourRowsHitMask,
    BingoHitMask
};

int32 BonusBingoGameResult::GetPayout( BonusBingo::WinPatterns pattern )
{
    return BonusBingoGameResult::g_Payouts[pattern];
}

/******************************************************************************
Construction/Destruction
******************************************************************************/
BonusBingoGameResult::BonusBingoGameResult(BonusBingoGame* pGame)
{

    m_jokerValue = 0;
    m_game = pGame;
    m_cardWins.resize( BonusBingo::NUM_MAX_CARDS );
    m_restoring = false;
}

BonusBingoGameResult::~BonusBingoGameResult()
{
    for( int index = 0 ; index < BonusBingo::NUM_MAX_CARDS ; ++index )
    {
        delete m_cardWins[index];
    }
}

const BonusBingoGame& BonusBingoGameResult::getGame() const
{
	ASSERT( m_game );
	return *m_game;
}

void BonusBingoGameResult::setJokerValue( uint8 value )
{
    m_jokerValue = value;
}

int32 BonusBingoGameResult::getJokerValue() const
{
    return m_jokerValue * m_game->getPerCardBet();
}

const BonusBingoGameResult::BallSet& BonusBingoGameResult::getSelectedBalls() const
{
    ASSERT( m_selectedBalls.size() == BonusBingo::NUM_BALLS_CHOSEN );
    return m_selectedBalls;
}

bool8 BonusBingoGameResult::addSelectedBall( uint8 ball )
{
    if ( !m_restoring )
    {
        //m_internalSelectedBalls is a set, so we can't add any duplicates
        //If we try, the returned value has false in the "second" member
        return m_internalSelectedBalls.insert( ball ).second;
    }
    else
    {
        m_selectedBalls.push_back( ball );
        return true;
    }
}

void BonusBingoGameResult::evaluateGame()
{
    //Here's where we find out how each card being played
    //works out against the drawn balls.

    //Get each card
    for( int card = 0 ; card < BonusBingo::NUM_MAX_CARDS; ++card )
    {        
        const BonusBingoCard* pCard = m_game->getCard( card );

        if ( !pCard->isActive() )
        {
            //Card not played.
            continue;
        }

        //Get the hit mask for the card
        HitMask cardMask( 0 );

        getHitMask( pCard, cardMask );

        //Check the hit mask against each hit evaluator
        for( int pattern = BonusBingo::SLASH; 
             pattern < BonusBingo::WIN_PATTERN_COUNT; 
             ++pattern)
        {
            HitMask result = cardMask;

            HitMask testMask = HitMasks[pattern];

            bool8 winner = false;
            
            winner = BonusBingo::evaluateHitMask( result, testMask );

            if ( winner )
            {
                //Record that this card won on this pattern
                addWin( card, result, (BonusBingo::WinPatterns)pattern );
            }
        }
    }
}

void 
BonusBingoGameResult::addWin(uint8 cardIndex,  HitMask hitMask, 
                             BonusBingo::WinPatterns pattern )
{
    if ( !m_cardWins[cardIndex] )
    {
        m_cardWins[cardIndex] = new WinVector;
    }

    WinVector& wins = *m_cardWins[cardIndex];

    WinHitMaskPair pair (pattern,  hitMask);
    
    //See if this new win overlaps any existing ones
    WinVector::iterator overlapped = std::remove_if( wins.begin(), 
                                                     wins.end(), 
                                                     WinOverlappedBy( pair ) );
	//Erase the removed wins.
	wins.erase( overlapped, wins.end() );

    wins.push_back( pair );
}

void BonusBingoGameResult::getHitMask(const BonusBingoCard* pCard, HitMask& hitMask ) const
{
#ifndef DEBUG_KNOWNSPINS
    //In known spin builds, this will be called earlier, to figure out if a new
    //ball will cause a new win.  Therefore, this assert will not be valid.
    ASSERT( m_internalSelectedBalls.size() == BonusBingo::NUM_BALLS_CHOSEN );
#endif

    InternalBallSet::const_iterator notFound ( m_internalSelectedBalls.end() );
        
    for( int column = BonusBingoCard::COLUMN_B;
             column <= BonusBingoCard::COLUMN_O;
             ++column )
    {
        for( int row = 0 ; row < 5 ; ++row )
        {
            uint8 cardValue = pCard->getValue( column, row );
            
            if( INDEX(column, row) == BonusBingoCard::NUM_WILD_INDEX
                || notFound != m_internalSelectedBalls.find(cardValue) )
            {
                //Then it's a hit...
                hitMask |= HIT_AT(column, row);
            }
        }
    }
}

void BonusBingoGameResult::finalizeResults()
{
    ASSERT( !m_restoring );

    //Move the sorted set of balls into the unsorted collection
    //This makes sure the final, shuffled sequence sent to the client
    //is repeatable from the database
    std::copy( m_internalSelectedBalls.begin(), m_internalSelectedBalls.end(),
                std::back_inserter( m_selectedBalls ) );
    
    std::random_shuffle( m_selectedBalls.begin(), m_selectedBalls.end() );

    m_internalSelectedBalls.clear();
}

void BonusBingoGameResult::startRestore()
{
    m_restoring = true;
}

void BonusBingoGameResult::endRestore()
{
    m_restoring = false;
}

uint8 BonusBingoGameResult::winsForCard( uint8 cardIndex ) const
{
    if ( m_cardWins[cardIndex] )
    {
        WinVector& wins = *m_cardWins[cardIndex];
        return wins.size();
    }
    return 0;
}

BonusBingo::WinPatterns
BonusBingoGameResult::getWinPattern( uint8 cardIndex, uint8 winIndex )  const
{
    if ( m_cardWins[cardIndex] )
    {
        WinVector& wins = *m_cardWins[cardIndex];
        
        if ( !wins.empty() && winIndex < wins.size() )
        {
            return wins[winIndex].first;
        }
    }
    return BonusBingo::LOSER;
}

uint8 BonusBingoGameResult::multiplierValue( uint8 cardIndex ) const
{
    if ( m_game->getMultipliedCardIndex() == cardIndex )
    {
        return m_game->getMultiplierValue();
    }
    else
    {
        return 1;
    }
}

int32 BonusBingoGameResult::payout( uint8 cardIndex ) const
{
    int payout = 0;    
    
    if ( m_cardWins[cardIndex] )
    {
        WinVector& wins = *m_cardWins[cardIndex];
        
        if ( !wins.empty() )
        {                
            for( WinVector::const_iterator it( wins.begin() ), end( wins.end() );
                 it != end;
                 ++it)
            {
                payout += g_Payouts[ it->first ] * m_game->getPerCardBet();
            }
            
            if ( cardIndex == m_game->getMultipliedCardIndex() )
            {
                payout *= m_game->getMultiplierValue();
            }
        }
    }

    return payout;
}

#ifdef DEBUG_KNOWNSPINS
/******************************************************************************
Function name	: BonusBingoGameResult::needsLastBall
@brief	        Returns true if the specifed ball adds a win to the results
@return	bool8 
@param		uint8 ball
******************************************************************************/
bool8 BonusBingoGameResult::needsLastBall( uint8 ball ) const
{
    bool8 lastNeeded = false;

    //Get each card
    for( int card = 0 ; card < BonusBingo::NUM_MAX_CARDS; ++card )
    {    
        bool8 ballOnCard = false;

        const BonusBingoCard* pCard = m_game->getCard( card );

        if ( !pCard->isActive() )
        {
            //Card not played.
            continue;
        }

        int32 row, col;

        for( row = 0 ; row < BonusBingo::NUM_CARD_ROWS; ++row )
        {
            for( col = 0 ; col < BonusBingo::NUM_CARD_COLS; ++col )
            {
                if ( ball == pCard->getValue( col, row ) )
                {
                    //This card has this ball
                    //Need to continue, and see if it's going to win
                    ballOnCard = true;
                    break;
                }
            }

            if ( ballOnCard )
                break;
        }

        if ( ballOnCard )
        {            
            //Get the hit mask for the card
            HitMask cardMask( 0 );
            HitMask newMask( 0 );
            
            getHitMask( pCard, cardMask );

            //Add the location of the test ball
            newMask = cardMask | HIT_AT( col, row );
            
            //Check the hit mask against each hit evaluator
            for( int pattern = BonusBingo::SLASH; 
            pattern < BonusBingo::WIN_PATTERN_COUNT; 
            ++pattern)
            {
                HitMask oldResult = cardMask;
                HitMask newResult = newMask;
                
                HitMask testMask = HitMasks[pattern];
                
                bool8 winner = false;

                //it is the last needed for this card if the old
                //mask was a loser and the new one is not.                
                lastNeeded = !BonusBingo::evaluateHitMask( oldResult, testMask)
                             && BonusBingo::evaluateHitMask( newResult, testMask );    
                
                if ( lastNeeded )
                {
                    //This ball gives us a new win
                    break;
                }            
            }
        }

        if ( lastNeeded )
        {
            //This ball gives us a new win
            break;
        }
    }

    return lastNeeded;
}
#endif

////////////////////////////////////////////////////////////////////////////////////////
/// @fn     BonusBingoGameResult::getWinPayout( uint8 cardIndex, uint8 winIndex ) const
///
/// Returns the monetary amount of a particular win.
///
/// @param  cardIndex   -   identifies the card that the win occurred on
/// @param  winIndex    -   identifies which particular win on the specified card
///
/// @return the monetary amount associated with the win.  0 is returned if the card and
///         win indexes don't identify a valid win.
////////////////////////////////////////////////////////////////////////////////////////
int32 BonusBingoGameResult::getWinPayout( uint8 cardIndex, uint8 winIndex ) const
{
    int payout = 0;    
    
    // what kind of win was it?
    BonusBingo::WinPatterns eWinPattern = getWinPattern( cardIndex, winIndex );

    if(eWinPattern != BonusBingo::LOSER)    
    {
        // if we have a valid win, then figure out how much money it generated for the player:
        payout += g_Payouts[ eWinPattern ] * m_game->getPerCardBet();
    
        if ( cardIndex == m_game->getMultipliedCardIndex() )
        {
            payout *= m_game->getMultiplierValue();
        }
    }

    return payout;
}


int32 BonusBingoGameResult::getTotalPayout() const
{
	int32 total = 0;

	for( uint8 index = 0 ; index < BonusBingo::NUM_MAX_CARDS ; ++index )
	{
		if ( m_cardWins[index] ) 
		{
			total += payout( index );
		}
	}

    total += m_jokerValue * m_game->getPerCardBet();

    return total;
}


/******************************************************************************
Helper functor to determine if one win pattern overlaps another
******************************************************************************/
BonusBingoGameResult::WinOverlappedBy::WinOverlappedBy( WinHitMaskPair testPattern )
    : m_testPattern( testPattern )
{
}

bool BonusBingoGameResult::WinOverlappedBy::operator()(const WinHitMaskPair& rhs ) const
{ 
    bool8 overlaps = false;
    BonusBingo::WinPatterns rhsPattern = rhs.first;        

    //If the rhs pattern pays less than test, then it can't overlap it
    if ( BonusBingoGameResult::GetPayout(rhsPattern)   <
         BonusBingoGameResult::GetPayout(m_testPattern.first) )
    {        
        //Is the rhs win pattern overlapped by our member "test" pattern?        
        //This would be bad news - the win would already be in the container
        ASSERT( rhsPattern != m_testPattern.first );
        
        //If the test pattern's hit mask also satisfies the rhs win Pattern
        //then we overlap.
        overlaps = (m_testPattern.second & rhs.second) == rhs.second;
        
#ifdef DEBUG
        //In Debug, make sure we're not evaluating an rhs that overlaps the test pattern
        //This would mean that we've tried to insert what would we be an overridden win
        //AFTER we added the win that overrides it.  We'd never get another chance to 
        //remove the lesser win after that...
        switch( m_testPattern.first )
        {
        case BonusBingo::CROSS:        
            ASSERT( rhsPattern != BonusBingo::BINGO );
            break;
        case BonusBingo::THREE_LINES_HORIZ:
            ASSERT( rhsPattern != BonusBingo::FOUR_LINES_HORIZ );
            break;
        case BonusBingo::PERIMETER:
            ASSERT( rhsPattern != BonusBingo::BINGO );
            break;
        case BonusBingo::SLASH:
        case BonusBingo::BACKSLASH:
        case BonusBingo::CORNERS:
            ASSERT( rhsPattern != BonusBingo::CROSS && rhsPattern != BonusBingo::BINGO );
            break;
        case BonusBingo::TWO_LINES_HORIZ:
            ASSERT( rhsPattern != BonusBingo::THREE_LINES_HORIZ && rhsPattern != BonusBingo::FOUR_LINES_HORIZ );
            break;
        }
#endif
    }

#ifdef DEBUG
    if ( overlaps )
        DEBUGMSG( ( "Win %d overlaps win %d.", m_testPattern.first, rhsPattern ) );
#endif

    return overlaps == true;
}